
<!DOCTYPE html>
<!-- 
  s60sc 2022, 2023
  with ideas from @marekful
-->
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>ESP32-CAM_MJPEG2SD</title>
    <link rel="icon" href='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y="80" font-size="80">📷</text></svg>'>
    
    <style>
      :root {
        /* colors used on web pages - see https://www.w3schools.com/colors/colors_names.asp */
        --buttonReady: crimson;
        --buttonActive: ForestGreen;
        --buttonHover: green;
        --buttonText: white;
        --buttonLabel: black;
        --itemInactive: gray;
        --menuBackground: transparent; 
        --inputText: navy;
        --pageText: WhiteSmoke; 
        --inputBackground: WhiteSmoke; 
        --alertMsg: orange;
        --itemBorder: silver; 
        --pageBackground:#181818; /* nearly black */
        
        /* logcolors */
        --warnColor: orange;
        --errColor: red;
        --chkColor: green;
        --dbgColor: blue;
        
        /* element sizes */
        --bodySize:1rem; /* used as basis for element, padding, and font sizes */
        --buttonSize: var(--bodySize);
        --buttonHalf: calc(var(--buttonSize) / 2);
        --buttonQuart: calc(var(--buttonSize) / 4);
        --buttonDbl: calc(var(--buttonSize) * 2);
        --inputSize: calc(var(--bodySize) * 0.9);
        --headingSize: calc(var(--bodySize) * 1.25);
        --subheadingSize: var(--bodySize);
        --footerSize: calc(var(--bodySize) * 0.7);
        --smallThumbSize: 1rem; /* must be specific value, not calc */
        --bigThumbSize: 2rem; /* must be specific value, not calc */
        --markerSize: 1rem; /* must be specific value, not calc */
      }
      
      html body {height: 100%;}
      
      body {
        font-family: Helvetica  !important;
        background: var(--pageBackground);
        color: var(--pageText);
        font-size: var(--bodySize); 
      }

      .hidden {
        display: none;
      }
      
      progress {
        accent-color: var(--buttonActive); 
        width: calc(var(--buttonSize) * 15);
      }
      
      .fixed {
        position: fixed;
        top: 0;
      }
      
      .alertMsg {
        left:10%;
        bottom: var(--buttonDbl);
        position:absolute;
        display:block;
        color:var(--alertMsg);
        z-index:20;
        height: var(--buttonDbl);
      }

      .header {
        font-size: var(--headingSize);
        padding-left: var(--buttonHalf);
        font-weight:bold; 
      }
      
      .subheader {
        font-style: italic;
        font-size: var(--subheadingSize);
        padding-left: var(--buttonQuart);
      }
      
      .tab {
        display: flex;
        overflow: hidden;
        background: var(--menuBackground);
      }

      .tab button {
        background: var(--buttonReady);        
        float: left;
        border: none;
        outline: none;
        cursor: pointer;
        padding: var(--buttonHalf);
        transition: 0.3s;
        font-size: calc(var(--buttonSize) * 1.1);
      }
      
      .tab button.active {
        box-shadow: 0 0 0 var(--buttonQuart) var(--buttonActive);
        background: var(--buttonActive);
      }
      
      .tab button.hover {
        background: var(--buttonHover);
      }
      
      .tab button.disabled {
        cursor: default;
        background: var(--itemInactive)
      }
      
      .tabcontent {
        display: none;
      }

      .grid-cols3 {
        grid-template-columns: calc(var(--buttonSize) * 14) calc(var(--buttonSize) * 12) calc(var(--buttonSize) * 12);
      }

      .grid-cols4 {
        grid-template-columns: calc(var(--buttonSize) * 10) calc(var(--buttonSize) * 10) calc(var(--buttonSize) * 10) calc(var(--buttonSize) * 10)
      }

      .grid-cols3, .grid-cols4 {
        display: grid;
        background: none;
        text-align: center;
        font-size: var(--inputSize);
        fill: var(--buttonLabel);
        border: 0px solid var(--itemBorder);
        dominant-baseline: middle;
        text-anchor: middle;
      }
      
      .cfgTitle {
        grid-column: 1/5;
        text-align: left;
      }

      svg {
        width: calc(var(--buttonSize) * 8);
        height: var(--buttonDbl);
      }
      
      rect {
        fill: var(--buttonReady);
        width: 100%;
        height: 100%; 
        x: 0;
        y: 0;
        ry: 15%;
      }
      
      rect:active {
        fill: var(--buttonActive);
      }
      
      rect:hover{
        fill: var(--buttonHover);
      }
    
      text {
        font-size: var(--buttonSize); 
        fill: var(--buttonText);
        transform: translate(50%, 50%);
        pointer-events: none; 
      } 
     
      .panel {
        padding: 0 var(--buttonSize);
        display: none;
        background-color: var(--pageBackground);
        overflow: hidden;
      }
      
      button {
        display: block;
        margin: var(--buttonQuart);
        padding: var(--buttonQuart);
        border: 0;
        line-height: var(--headingSize);
        cursor: pointer;
        color: var(--buttonText);
        background: var(--buttonReady);
        border-radius: var(--buttonQuart);
        font-size: var(--buttonSize);
        outline: 0
      }

      button:active {
       box-shadow: 0 0 0 var(--buttonQuart) var(--buttonActive);
       background: var(--buttonActive);
      }

      button:hover {
        background: var(--buttonHover);
      }

      button:disabled {
        cursor: default;
        background: var(--itemInactive)
      }
      
      .input-group {
        position: relative;
        display: flex;
        flex-wrap: nowrap;
        line-height: var(--buttonDbl);
        margin-top: var(--buttonHalf);
      }
      
      .input-group>label {
        display: inline-block;
        padding-right: var(--buttonHalf);
        min-width: 40%;
      }
      
      .input-group>input {
        width: 100%;
      }

      .input-group input,.input-group select {
        flex-grow: 1
      }
      
      input {
        height: calc(var(--inputSize) * 1.5);
        border-radius: var(--buttonQuart);
      }
      
      input, #appLog {
        font: var(--inputSize) 'Courier New';
        font-weight:bold;
        color: var(--inputText);
        background: var(--inputBackground);
        min-width: calc(var(--buttonSize) * 10);       
      }
      
      input[type="radio"], input[type="checkbox"], progress {
        accent-color: var(--buttonActive);
      }

      input[type="text"]{
        font-size: var(--inputSize); 
        width: calc(var(--buttonSize) * 6);
      }
      
      input[type="checkbox"] {
        width : calc(var(--inputSize) * 1.5);
        height : calc(var(--inputSize) * 1.5);
        margin-top: calc(var(--buttonQuart) * -1);
      }
      
      input[type="radio"] {
        width: var(--inputSize);
        height: var(--inputSize);
        min-width: var(--buttonSize);
      }
      
 /*     input[type="number"]:not(.configItem), input[type="time"] {
        font-size: var(--buttonDbl);
        border: 1px solid var(--itemBorder);
        margin-bottom: var(--buttonQuart);
        min-width: calc(var(--buttonSize) * 7);
        text-align: center;
      } */
      input[type="number"] {
        height: var(--buttonSize);
      }
      
      input[type=number]::-webkit-inner-spin-button {
        opacity: 1;
      }
      
      #appLog {
        height:50vh;
        width:90%;
        border:2px solid var(--itemBorder);
        overflow:auto;
        background: var(--inputBackground);
      }
      
      select {
        border: 1px solid var(--menuBackground);
        font-size: var(--inputSize);
        outline: 0;
        border-radius: var(--buttonQuart);
        margin-top: 2px;
      }
      
      .selectField {
         height: calc(var(--inputSize) * 1.5);
      }
      
      table {
        border-collapse:collapse; 
        table-layout:fixed;
        background: var(--menuBackground);
        text-align: left;
      }
      
      th, td {
        font-weight:bold; 
      }
      
      .config-group th, .config-group td {
        border: 0;
        padding: var(--buttonHalf) var(--buttonHalf) 0 var(--buttonHalf); 
        padding-left: var(--buttonSize);
        line-height: var(--buttonDbl);
        font-size: var(--buttonSize);
      }
      
      .configGroup td input[type="number"] {
        font-size: var(--buttonSize);
      }
      
      input.configItem[type="radio"] {
        min-width: var(--buttonSize); 
      }
      
      input.configItem[type="number"] {
        min-width: calc(var(--buttonSize) * 3); 
        width: calc(var(--buttonSize) * 3); 
      }
      
      .svgCols {
        width: calc(var(--buttonSize) * 9); 
        height: calc(var(--buttonSize) * 2.5);  
        fill: var(--buttonLabel);
        font-size: calc(var(--buttonSize) * 1.5);
      }   
      
      .svgIcon {
        width: var(--buttonDbl);
        height: var(--buttonDbl);
      }
      
      rect.RCon {
        fill: var(--buttonActive);
      }
      
      rect.RCoff {
        fill: var(--buttonReady);
      }
      
      .upperText {
        transform: translate(50%,30%);  
      }
      
      .lowerText {
        transform: translate(50%,70%);  
      }
      
      .midText {
        transform: translate(50%,50%); 
      }
      
      
      /*** range slider ***/
      
      .input-group>input[type=range]  {
        min-width: calc(var(--markerSize) * 8);  
      }
      
      input[type=range] {
        -webkit-appearance: none;
        background: transparent;
      }

      input[type=range]::-webkit-slider-runnable-track {
        -webkit-appearance: none;
        width: 100%;
        height: 2px;
        background: var(--inputBackground); 
        margin-top: 2px;
      }

      input[type=range]::-webkit-slider-thumb {
        /* location of thumb relative to track */
        height: var(--smallThumbSize);
        width: var(--smallThumbSize);
        border-radius: 50%;
        background: var(--buttonReady); 
        -webkit-appearance: none;
        margin-top: calc(-0.5 * var(--smallThumbSize));
      }
      input[type=range].bigThumb::-webkit-slider-thumb{
        height: var(--bigThumbSize);
        width: var(--bigThumbSize);
        margin-top: calc(-0.5 * var(--bigThumbSize));
      }
      
      div[name="rangeVal"] {
        /* shape & location of marker relative to thumb */
        width: calc(var(--markerSize) * 2);
        height: var(--markerSize);
        margin-top: calc(-0.5 * var(--smallThumbSize));
        line-height: var(--markerSize);
        text-align: center;
        background: var(--buttonReady); 
        color: var(--buttonText); 
        font-size: var(--footerSize);
        display: inline;
        position: absolute;
        border-radius: 25%;
        pointer-events: none;
      }
      div[name="rangeVal"].rvThumb {
        margin-top: calc(-0.5 * var(--bigThumbSize) - var(--buttonQuart));
      }

      div[name="rangeMin"], div[name="rangeMax"] {
        display: inline-block;
        padding: 0 var(--buttonQuart);
        pointer-events: none;
      }
       

      /* checkbox slider */

      /* Hide the default checkbox */
      .switch input[type="checkbox"] {
        display: none;
      }

      /* static part */
      .switch .slider {
        width: calc(var(--buttonSize) * 3);  
        height: var(--buttonSize);
        top: calc(var(--buttonQuart) / 2);
        background: var(--itemInactive);
        position: relative;
        display: inline-block;
        border-radius: var(--buttonSize);
      }
      .switch input[type="checkbox"]:checked + .slider {
        background-color: var(--buttonActive);
      }

      /* moving part */
      .switch .slider::before {
        content: "";
        height: var(--buttonSize);
        width: var(--buttonSize);
        background: var(--inputBackground);
        position: absolute;
        left: var(--buttonQuart);
        border-radius: var(--buttonSize);
        transition: transform 0.4s;
      }
      .switch input[type="checkbox"]:checked + .slider::before {
        transform: translateX(calc(var(--buttonSize)*1.5));
      }


      .navtop{
        list-style: none;
        border: 1px solid var(--itemBorder);
        border-radius: var(--buttonQuart);
        padding: var(--buttonQuart);
        margin-top: var(--buttonHalf);
      }
      .navtop li {
       float: left;
       position: relative;
      }

      nav.menu {
        display: grid;
        flex-direction: column;
        flex-wrap: nowrap;
        min-width: calc(var(--buttonSize) * 20);
        background: var(--menuBackground);
        padding: var(--buttonHalf);
        border-radius: 0 var(--buttonQuart) var(--buttonQuart) var(--buttonQuart);
        margin-bottom: var(--buttonQuart);
        border: 1px solid var(--itemBorder);
      }
      nav.menu.buttons {
        min-width: calc(var(--buttonSize) * 25);
      }
      nav.menu.panel {
        display: none;
        min-height: calc(var(--buttonSize) * 20);
      }
      nav.menu.panel.active {
        display: block;
      }
      
      nav.quick-nav {
        width: var(--buttonDbl);
        height: var(--buttonDbl);
        margin: var(--buttonHalf) var(--buttonHalf); var(--buttonHalf) var(--buttonHalf);
        border: 1px solid var(--itemBorder);
        border-radius: var(--buttonQuart);
        cursor: pointer;
        font-size: calc(var(--buttonSize) * 1.5);
        padding: var(--buttonQuart) var(--buttonQuart) var(--buttonQuart) var(--buttonQuart);
        background: var(--buttonReady);
      }

      nav.quick-nav:hover {
        background: var(--buttonHover);
      }

      nav.quick-nav.active {
        box-shadow: 0 0 0 var(--buttonQuart) var(--buttonActive);
        background: var(--buttonActive);
      }
       
      nav#maintoolbar {
        display: flex;
        flex-wrap: nowrap;
        justify-content: flex-end;
        overflow: auto;
      }
      
      .nav-toggle {
        margin: 0 0 var(--buttonQuart) var(--buttonQuart);
        font-size:var(--headingSize);
        display: block;
        margin-bottom: calc(var(--buttonSize) * 3/4);
        padding-bottom: var(--buttonHalf);
        border-bottom: 1px solid var(--itemBorder);
      }
                  
      section#main {
        display: flex;
        flex-direction: column;
      }
      section#header {
        min-width: calc(var(--buttonSize) * 20);
        background: var(--menuBackground);
        margin-bottom: 2px;
        padding: 1px 1px;
        display: flex;
        flex-wrap: wrap;
        border-radius: 2px;
        justify-content: space-between;
      } 
      section#title{
        display: flex;
      }
      section#footer {
        position: fixed;
        bottom: 0;
        width: 97%;
        min-width: calc(var(--buttonSize) * 20);
        background: var(--menuBackground);
        margin-top: 0px;
        padding: var(--buttonQuart) var(--buttonHalf);
        display: flex;
        flex-wrap: wrap;
        border-radius: var(--buttonQuart) var(--buttonQuart) 0 0;
        justify-content: space-between;
        font-size: var(--footerSize);
        z-index: -1;
        border-top: 1px solid var(--itemBorder);
        border-left: 1px solid var(--itemBorder);
        border-right: 1px solid var(--itemBorder);
        border-bottom: none;
        text-align: center; 
      }
      
      section#buttons {
        display: flex;
        flex-wrap: nowrap;
        justify-content: center;
      }

      #foot-spacer {
        height: calc(var(--buttonSize) * 4);
      }
      
      .info-group {
        position: relative;                               
        margin: var(--buttonQuart) 0;
      }
      .info{
        margin-top: 2px;
      }
      .info-group label {
        color: var(--itemInactive);
      }
      
      .menu-action {
        display: none;
      }

      .menu-action + label + div {
        padding: var(--buttonHalf) 0 0 0;
        margin: var(--buttonQuart) 0 0 0;
        border-top: 1px solid var(--itemBorder);
      }

      .menu-action:not(:checked) + label + div { 
        display: none; 
      }

      .sep {
        border: 1px solid var(--itemBorder);
        height: var(--buttonDbl);
        margin: var(--buttonQuart) var(--buttonQuart) 0 var(--buttonQuart);
      }
      .vsep {
        border: 1px solid var(--itemBorder);
        margin: var(--buttonHalf) 2px;
      }

      #menu-top.menu-floating nav.menu.panel {
        border-radius: 0 var(--buttonQuart) var(--buttonQuart) var(--buttonQuart);
      }
      #menu-top.menu-pinned nav.menu.panel {
        border-radius: var(--buttonQuart) var(--buttonQuart) var(--buttonQuart);
        height: 110%;
        left: -1px;
        position: relative;
      }
      #menu-container {
        position: relative;
        min-width: calc(var(--buttonSize) * 4);
        min-height: calc(var(--buttonSize) * 20);
        border-radius: var(--buttonQuart);
        left:var(--buttonQuart);
        top: var(--buttonQuart);
      }
      #menu-container.menu-floating {
        display: table;
      }
      #menu-container.menu-pinned {
        display: flex;
      }
      #menu-top {
        display: inline-grid;
      }
      #menu-top.menu-floating {
        position: absolute;
        left: calc(var(--buttonSize) * 4);
        z-index: 2;
      }
      #menu-top.menu-pinned {
        position: initial;
        left: initial;
        float: left;
        z-index: 4;
        border-left: none;
      }
      #menu-selector {
        z-index: 4;
        background: var(--menuBackground);
        width: calc(var(--buttonSize) * 4);
      }
      #menu-selector.menu-floating {
        position: absolute;
      }
      #menu-selector.menu-pinned {
        position: relative;
        float: left;
        height: 100%;
        border-right: none;
        z-index: 5;
      }
      .menu-floating.menu-open {
        border-radius: var(--buttonQuart) 0 0 var(--buttonQuart);
        border: 1px solid var(--itemBorder);
        border-right: none;
      }
      .menu-floating.menu-closed {
        border-radius: var(--buttonQuart);
        border: 1px solid var(--itemBorder);
      }
      .menu-pinned.menu-open {
        border-radius: var(--buttonQuart) 0 0 var(--buttonQuart);
        border: 1px solid var(--itemBorder);
      }
      .menu-pinnded.menu-closed {
        border-radius: var(--buttonQuart);
        border: 1px solid var(--itemBorder);
      }
      .pin-menu {
        width: calc(var(--buttonSize) * 3/2);
        height: calc(var(--buttonSize) * 3/2);
        float: right;
        cursor: pointer;
      }

      .displayonly {
        pointer-events: none;
      }
      
      .blinking {
        animation: blinker 1.5s linear infinite;
      }

      @keyframes blinker {
        50% {
          opacity: .4;
        }
      }
      
      figure {
        padding: 0px;
        margin: 0; 
        width: 100%;
        height: auto;
        margin-block-start: 0;
        margin-block-end: 0;
        margin-inline-start: 0;
        margin-inline-end: 0;
      }
      
      .image-container {
        position: relative;
      }

      figure img {
        display: block;
        width: 100%;
        height: 100%;
        border-radius: var(--buttonQuart);
      }
      
      #content {
        display: flex;
        flex-wrap: wrap;
        align-items: stretch
      }

      #sidebar{
        margin: 0px;
        padding: 0px; 
        width: 100%;
      } 

      @media (min-width: 50rem) and (orientation:landscape) {
        #content {
          display:flex;
          flex-wrap: nowrap;
          align-items: stretch
        }
        #sidebar {
          width: auto;
        } 
      }
      
      #recording-indicator {
        display: inline-block;
        width: calc(var(--buttonSize) * 3/4);
        height: calc(var(--buttonSize) * 3/4);
        padding: var(--buttonQuart);
        margin: var(--buttonHalf);
        background: var(--buttonReady);
        border-radius: var(--buttonSize);
        border: 2px solid var(--itemBorder);
      }
      
      #stream-container {
        margin: var(--buttonQuart) var(--buttonHalf); 
        border: 1px solid var(--itemBorder);
      }
      
      .iconSize{
        position: absolute;
        top: var(--buttonQuart);
        background: var(--buttonReady);
        width: var(--buttonSize);
        height: var(--buttonSize);
        border-radius: calc(var(--buttonSize) * 6);
        color: var(--buttonText);
        text-align: center;
        line-height: var(--buttonSize);
        cursor: pointer;
        text-decoration: none;
        z-Index: 1;
      }
      
      .RCcontrolFmt {
        position: absolute;
        background: var(--buttonReady);
        width: var(--buttonDbl);
        height: var(--buttonDbl);
        border-radius: calc(var(--buttonSize) * 6);
        color: var(--buttonText);
        text-align: center;
        line-height: var(--buttonSize);
        cursor: pointer;
        text-decoration: none;
        dominant-baseline: middle;
        text-anchor: middle;
        z-Index: 1;
      }

      .RCbuttonFmt {
        left: 1px; 
        top: 1px;
      }
        
      .RCmotorFmt {
        background: transparent;
        left: -56px; 
        bottom: 72px;
      }
      
      .RClightsFmt {
        left: 160px; 
        bottom: 8px;
      }
      
      .RCsteerFmt {
        background: transparent;
        right: 140px; 
        bottom: 4px;
      }
      
      fieldset {
        margin: 0px;
      }
      
      
      /* hub page */
      
      #imageContainer {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(20%, 30%));
        gap: var(--buttonSize);
        margin-left: var(--buttonDbl); 
      }

      .hubImg {
        max-width: 100%;
        height: auto;
      }

      .ipContainer {
        position: relative;
        cursor: pointer;
      }

      .ipText {
        position: absolute;
        bottom: var(--buttonQuart);
        left: 50%; /* Center the overlay horizontally */
        transform: translateX(-50%); /* Adjust for centering */
        font-size: var(--buttonSize);
        color: var(--pageText);
        padding: var(--buttonQuart);
      }

      #buttonContainer {
        margin-bottom: calc(var(--buttonSize) * 4);
        display: flex;
        align-items: center;
      }

    </style>
  </head>
  
  <body>
    <div class="tab">
      <ul class="navtop">
        <button class="tablinks camTab active" name="mainPage">Camera</button>
        <button class="tablinks" name="ShowLog">Show Log</button>
        <button class="tablinks" name="EditConfig">Edit Config</button>
        <button class="tablinks" name="DeviceHub" style="display: none;">Camera Hub</button>
        <a href="/web?OTA.htm"><button class="tablinks">OTA Upload</button></a>
      </ul>
      <section id="main">
        <nav id="maintoolbar">
          <ul class="navtop">
            <li><span id="recording-indicator" style="float: right; display: none;"></span></li>
            <li><button id="forceRecord" style="float:right;">➤&nbsp;Start Recording</button></li>   
            <li><div class="sep"></div></li>
            <li><button id="forceStream" class="local" style="float:right;">➤&nbsp;Start Stream</button></li>
            <li><button id="get-still" class="local" style="float:right;">Get Still</button></li>
            <li><div class="sep"></div></li>
            <li><button id="forcePlayback" class="local" style="float:right;" disabled="disabled" title="Select a video in 'Playback & File Transfers' to activate playback.">➤&nbsp;Start Playback</button></li>
          </ul>
        </nav>
      </section>
    </div>
    
    <div id="mainPage" class="tabcontent" style="display:block">
      <div id="content">
        <div id="mainPageIcons">
          <div id="sidebar">
            <div id="menu-container" class="menu-pinned">
              <nav id="menu-selector" class="menu-pinned menu-closed">
                <nav class="quick-nav" id="camControl" name="camera-control">📷</nav>
                <nav class="quick-nav" id="motionRec" name="motion-record">👀</nav>
                <nav class="quick-nav" id="fileControl" name="playback-transfers">🎥</nav>
                <nav class="quick-nav" id="picSettings" name="picture-settings">📺</nav>
                <nav class="quick-nav" id="accSettings" name="access-settings">🔧</nav>
              </nav>
              <div id="menu-top" class="menu-pinned">
                <nav class="menu panel" id="camera-control">
                  <input type="checkbox" id="control-cb" class="menu-action" checked="checked">
                  <div class="pin-menu" title="Pin / unpin menu">📌</div>
                  <label for="control-cb" class="nav-toggle">📷&nbsp;&nbsp;Camera Control&nbsp;&nbsp;</label>
                  <div class='addButtons'></div>
                  <div>                    
                    <div class="input-group" id="framesize-group">
                      <label for="framesize">Resolution</label>
                      <select id="framesize" class="selectField">
                        <option class="OV5640 hidden" value="21">QSXGA(2560x1920)</option>
                        <option class="OV5640 hidden" value="20">P_FHD(1080x1920)</option>
                        <option class="OV5640 hidden" value="19">WQXGA(2560x1600)</option>
                        <option class="OV5640 hidden" value="18">QHD(2560x1440)</option>
                        <option class="OV3660 OV5640 hidden" value="17">QXGA(2048x1564)</option>
                        <option class="OV3660 hidden" value="16">P_3MP(864x1564)</option>
                        <option class="OV3660 OV5640 hidden" value="15">P_HD(720x1280)</option>
                        <option class="OV3660 OV5640 hidden" value="14">FHD(1920x1080)</option>
                        <option value="13">UXGA(1600x1200)</option>
                        <option value="12">SXGA(1280x1024)</option>
                        <option value="11">HD(1280x720)</option>
                        <option value="10">XGA(1024x768)</option>
                        <option value="9" selected="selected">SVGA(800x600)</option> 
                        <option value="8">VGA(640x480)</option> 
                        <option value="7">HVGA(480x320)</option>
                        <option value="6">CIF(400x296)</option> 
                        <option value="5">QVGA(320x240)</option>
                        <option value="4">240x240</option> 
                        <option value="3">HQVGA(240x176)</option> 
                        <option value="2">QCIF(176x144)</option> 
                        <option value="1">QQVGA(160x120)</option> 
                        <option value="0">96x96</option>
                      </select>
                    </div>
                  </div>
                  <div class="input-group" id="fps-group">
                      <label for="fps">FPS</label>
                      <input title="Set camera required frames per second" type="range" id="fps" min="1" max="30" value="10">
                  </div>
                  <div class="input-group" id="quality-group">
                      <label for="quality">Quality</label>
                      <input title="Set the recording quality" type="range" id="quality" min="4" max="63" value="10">
                  </div>                          
                  <div class="input-group" id="micGain-group">
                    <label for="micGain">Microphone Gain</label>
                    <input title="Set microphone gain" type="range" id="micGain" min="0" max="10" value="0">
                  </div>
                  <div class="input-group" id="CamTilt-group">
                    <label for="camTilt">Camera Tilt</label>
                    <input title="Set camera tilt angle" type="range" id="camTilt" min="0" max="180" value="90">
                  </div>
                  <div class="input-group" id="CamPan-group">
                    <label for="camPan">Camera Pan</label>
                    <input title="Set camera pan angle" type="range" id="camPan" min="0" max="180" value="90">
                  </div>
                  <div class="input-group" id="lamp-group">
                    <label for="lamp">Lamp</label>
                     <input title="Control onboard lamp led brightness" type="range" id="lampLevel" min="0" max="15" value="0">
                  </div>
                </div>
              </nav>
              <nav class="menu panel" id="picture-settings">                                                   
                <input type="checkbox" id="settings-cb" class="menu-action">
                <div class="pin-menu" title="Pin / unpin menu">📌</div>
                <label for="settings-cb" class="nav-toggle">📺&nbsp;&nbsp;Picture Settings&nbsp;&nbsp;</label>
                <div class='addButtons'></div>
                <div>
                  <div class="input-group" id="xclk-group">
                      <label for="set-xclk">Clock MHz</label>
                      <div class="text">
                          <input id="xclkMhz" title="Set camera clock speed in MHz" type="number" minlength="1" maxlength="2" size="2" value="20">
                      </div>
                  </div>
                  <div class="input-group" id="brightness-group">
                    <label for="brightness">Brightness</label>
                    <input type="range" id="brightness" min="-2" max="2" value="0">
                  </div>
                  <div class="input-group" id="contrast-group">
                    <label for="contrast">Contrast</label>
                    <input type="range" id="contrast" min="-2" max="2" value="0">
                  </div>
                  <div class="input-group" id="saturation-group">
                    <label for="saturation">Saturation</label>
                    <input type="range" id="saturation" min="-2" max="2" value="0">
                  </div>
                  <div class="OV3660 OV5640 hidden">
                    <div class="input-group" id="sharpness-group">
                      <label for="sharpness">Sharpness</label>
                      <input type="range" id="sharpness" min="-3" max="3" value="0">
                    </div>
                    <div class="input-group" id="denoise-group">
                      <label for="denoise">De-Noise</label>
                      <div name="rangeMin">Auto</div>
                      <input type="range" id="denoise" min="0" max="8" value="0">
                    </div>
                  </div>
                  <div class="input-group" id="special_effect-group">
                    <label for="special_effect">Special Effect</label>
                    <select id="special_effect" class="selectField">
                      <option value="0" selected="selected">No Effect</option>
                      <option value="1">Negative</option>
                      <option value="2">Grayscale</option>
                      <option value="3">Red Tint</option>
                      <option value="4">Green Tint</option>
                      <option value="5">Blue Tint</option>
                      <option value="6">Sepia</option>
                    </select>
                  </div>
                  <div class="input-group" id="awb-group">
                      <label for="awb">AWB Enable</label>
                      <div class="switch">
                          <input id="awb" type="checkbox" checked="checked">
                          <label class="slider" for="awb"></label>
                      </div>
                  </div>
                  <div class="input-group" id="awb_gain-group">
                    <label for="awb_gain" id="awbg_lab">AWB Gain</label>
                    <div class="switch">
                      <input id="awb_gain" type="checkbox" checked="checked">
                      <label class="slider" for="awb_gain"></label>
                    </div>
                  </div>
                  <div class="input-group" id="wb_mode-group">
                    <label for="wb_mode">AWB Mode</label>
                    <select id="wb_mode" class="selectField">
                      <option value="0" selected="selected">Auto</option>
                      <option value="1">Sunny</option>
                      <option value="2">Cloudy</option>
                      <option value="3">Office</option>
                      <option value="4">Home</option>
                    </select>
                  </div>
                  <div class="input-group" id="aec-group">
                    <label for="aec">AEC Enable</label>
                    <div class="switch">
                      <input id="aec" type="checkbox" checked="checked">
                      <label class="slider" for="aec"></label>
                    </div>
                  </div>
                  <div class="input-group" id="aec_value-group">
                    <label for="aec_value">Manual Exposure</label>
                    <input type="range" id="aec_value" min="0" max="1200" value="204">
                  </div>
                  <div class="input-group" id="ae_level-group">
                    <label for="ae_level">Exposure Level</label>
                    <input type="range" id="ae_level" min="-2" max="2" value="0">
                  </div>
                  <div class="input-group" id="aec2-group">
                    <label for="aec2" id="aec2_lab">AEC DSP</label>
                    <div class="switch">
                      <input id="aec2" type="checkbox" checked="checked">
                      <label class="slider" for="aec2"></label>
                    </div>
                  </div>
                  <div class="input-group" id="dcw-group">
                    <label for="dcw" id="dcw_lab">DCW (Downsize)</label>
                    <div class="switch">
                      <input id="dcw" type="checkbox" checked="checked">
                      <label class="slider" for="dcw"></label>
                    </div>  
                  </div>
                  <div class="input-group" id="agc-group">
                    <label for="agc">AGC Enable</label>
                    <div class="switch">
                      <input id="agc" type="checkbox" checked="checked">
                      <label class="slider" for="agc"></label>
                    </div>
                  </div>
                  <div class="input-group hidden" id="agc_gain-group">
                    <label for="agc_gain">Gain</label>
                    <div name="rangeMin">1x</div>
                    <input type="range" id="agc_gain" min="0" max="30" value="5">
                    <div name="rangeMax">31x</div>
                  </div>
                  <div class="input-group" id="gainceiling-group">
                    <label for="gainceiling">Gain Ceiling</label>
                    <div name="rangeMin">2x</div>
                    <input type="range" id="gainceiling" min="0" max="6" value="0">
                    <div name="rangeMax">128x</div>
                  </div>       
                  <div class="input-group" id="lenc-group">
                    <label for="lenc">Lens Correction</label>
                    <div class="switch">
                      <input id="lenc" type="checkbox" checked="checked">
                      <label class="slider" for="lenc"></label>
                    </div>
                  </div>
                  <div class="input-group" id="hmirror-group">
                    <label for="hmirror">H-Mirror</label>
                    <div class="switch">
                      <input id="hmirror" type="checkbox" checked="checked">
                      <label class="slider" for="hmirror"></label>
                    </div>
                  </div>
                  <div class="input-group" id="vflip-group">
                    <label for="vflip">V-Flip</label>
                    <div class="switch">
                      <input id="vflip" type="checkbox" checked="checked">
                      <label class="slider" for="vflip"></label>
                    </div>
                  </div>
                  <div class="input-group" id="colorbar-group">
                    <label for="colorbar">Color Bar</label>
                    <div class="switch">
                      <input id="colorbar" type="checkbox">
                      <label class="slider" for="colorbar"></label>
                    </div>
                  </div>
                  <div class="input-group" id="bpc-group">
                    <label for="bpc">BPC</label>
                    <div class="switch">
                      <input id="bpc" type="checkbox" checked="checked">
                      <label class="slider" for="bpc"></label>
                    </div>
                  </div>
                  <div class="input-group" id="wpc-group">
                    <label for="wpc">WPC</label>
                    <div class="switch">
                      <input id="wpc" type="checkbox" checked="checked">
                      <label class="slider" for="wpc"></label>
                    </div>
                  </div>
                  <div class="input-group" id="raw_gma-group">
                    <label for="raw_gma">GMA Enable</label>
                    <div class="switch">
                      <input id="raw_gma" type="checkbox" checked="checked">
                      <label class="slider" for="raw_gma"></label>
                    </div>
                  </div>                  
                </div>
                <br><br>
              </nav>
              <nav class="menu panel" id="motion-record">
                <input type="checkbox" id="motion-cb" class="menu-action">
                <div class="pin-menu" title="Pin / unpin menu">📌</div>
                <label for="motion-cb" class="nav-toggle">👀&nbsp;&nbsp;Motion Detect & Recording&nbsp;&nbsp;</label>
                <div class='addButtons'></div>
                <div>
                  <div class="input-group" id="motion-group">
                    <label for="enableMotion">Motion Detect</label>
                    <div class="switch">
                      <input id="enableMotion" type="checkbox">
                      <label title="Enable/disable motion detection" class="slider" for="enableMotion"></label>
                    </div>
                  </div>
                  <div class="input-group" id="motion-group">
                    <label for="motion">Motion Sensitivity</label>
                    <input title="Set motion detection sensitivity" type="range" id="motionVal" min="1" max="10" value="7">
                  </div> 
                  <div class="input-group" id="minf-group">
                    <label for="minf">Min Seconds</label>
                    <input title="Minimum number of frames to be captured or the file is deleted" type="range" id="minf" min="0" max="20" value="5">
                  </div>                     
                  <div class="input-group" id="record-group">
                      <label for="record">Save Capture</label>
                      <div class="switch">
                        <input id="record" type="checkbox">
                        <label title="Enable recording on motion detection" class="slider" for="record"></label>
                      </div>
                  </div> 
                  <div class="input-group" id="dbgMotion-group">
                    <label for="dbgMotion">Show Motion</label>
                    <div class="switch">
                      <input id="dbgMotion" type="checkbox">
                      <label title="Display detected camera motion" class="slider" for="dbgMotion"></label>
                    </div>
                  </div>
                  <div class="input-group" id="lswitch-group">
                    <label for="lswitch">Night Switch</label>
                    <input title="Set night switch sensitivity" type="range" id="lswitch" min="0" max="100" value="10">
                  </div> 
                  <div class="vsep"></div>
                  <div class="input-group" id="tlapse-group">
                    <label for="timeLapseOn">Time Lapse</label>
                    <div class="switch">
                      <input id="timeLapseOn" type="checkbox">
                      <label title="Enable time lapse recording" class="slider" for="timeLapseOn"></label>
                    </div>
                  </div>
                </div>
                <br><br>
              </nav>
              <nav class="menu panel" id="playback-transfers">
                <input type="checkbox" id="files-cb" class="menu-action">
                <div class="pin-menu" title="Pin / unpin menu">📌</div>
                <label for="files-cb" class="nav-toggle">🎥&nbsp;&nbsp;Playback & File Transfers&nbsp;&nbsp;</label>
                <div class='addButtons'></div>
                <div>
                  <div class="input-group" id="sfiles-group" style="display: grid;">
                    <label for="sfiles">Select folder / file</label>                          
                    <select title="Select sd card file or folder" id="sfile" size="12">
                      <option value="/~reset" selected="selected">-- Select --</option>
                      <option value="/">Get Folders</option>
                      <option value="/~current">List current (today)</option>
                      <option value="/~previous">List previous (yesterday)</option>
                    </select>
                  </div>
                  <div>
                    <progress id="progressBar" value='0' max='100' class="info"></progress>%
                  </div>
                  <br>
                  <section id="buttons">
                    <button class="local" title="Download selected file from SD card" id="download" style="float:right" value="1">Download</button>
                    <button class="local" title="Upload selected file/folder to Fileserver" id="upload" style="float:left" value="1">File Upload</button>
                    <button class="local" title="Delete selected file/folder from SD card" id="delete" style="float:right" value="1">Delete</button>
                  </section>
  <!--
                  <div class="input-group" id="auth-group">
                    <label for="useFtps">Use FTPS</label>
                    <div class="switch">
                      <input id="useFtps" type="checkbox">
                      <label title="Use FTPS instead of FTP" class="slider" for="useFtps"></label>
                    </div>
                  </div>
  -->
                  <div class="input-group" id="autoUpload-group">
                    <label for="autoUpload">Auto upload</label>
                    <div class="switch">
                      <input id="autoUpload" type="checkbox">
                      <label title="Automatic upload to file server on file creation" class="slider" for="autoUpload"></label>
                    </div>
                  </div>
                  <div class="input-group" id="deleteAfter-group">
                    <label for="deleteAfter">Auto SD Delete</label>
                    <div class="switch">
                      <input id="deleteAfter" type="checkbox">
                      <label title="Automatic deletion on SD after file server upload" class="slider" for="deleteAfter"></label>
                    </div>
                  </div>
                </div>
                <br><br>
              </nav>
              <nav class="menu panel" id="access-settings">
                <input type="checkbox" id="other-cb" class="menu-action">
                <div class="pin-menu" title="Pin / unpin menu">📌</div>
                <label for="other-cb" class="nav-toggle">🔧&nbsp;&nbsp;Access Settings&nbsp;&nbsp;</label>
                <div class='addButtons'></div>
                <div>
                  <div class="subheader">Network settings</div>
                  <div class="input-group" id="wifi-group">
                    <label for="hostName">Host Name</label>
                    <input id="hostName" name="hostName" length=32 placeholder="Host name">
                  </div>
                  <div class="input-group" id="wifi-group">
                    <label for="ST_SSID">SSID</label>
                    <input id="ST_SSID" name="ST_SSID" length=32 placeholder="Router SSID">
                  </div>
                  <div class="input-group" id="wifi-group">
                    <label for="ST_Pass">Password</label>
                    <input id="ST_Pass" name="ST_Pass" length=64 placeholder="Router password">
                  </div>
                  <div class="input-group">
                      <label for="extIP">External&nbsp;IP</label>
                      <div id="extIP" class="displayonly">&nbsp;</div>
                  </div>
                  <br>
                  <div class="subheader">Clock settings</div>
                  <div class="input-group" id="time-group">
                    <label for="timezone">Time zone</label>
                    <input id="timezone" name="timezone" length=64 placeholder="Time zone string">
                  </div>
                  <div class="input-group" id="time-group">
                    <label for="timezoneSel">Time zone select</label>
                    <select id="timezoneSel" name="timezoneSel" class="selectField">
                      <option value="" selected>&nbsp;-- Select --</option>
                      <option value="EET-2EEST-3,M3.5.0/03:00:00,M10.5.0/04:00:00">Europe/Athens</option>
                      <option value="GMT0BST,M3.5.0/1,M10.5.0">Europe/Belfast</option>
                      <option value="CET-1CEST,M3.5.0,M10.5.0/3">Europe/Berlin</option>
                      <option value="GMT0BST,M3.5.0/1,M10.5.0">Europe/London</option>
                      <option value="CET-1CEST,M3.5.0,M10.5.0/3">Europe/Paris</option>
                      <option value="CET-1CEST,M3.5.0,M10.5.0/3">Europe/Rome</option>
                      <option value="CET-1CEST,M3.5.0,M10.5.0/3">Europe/Zurich</option>
                      <option value="<-12>12">GMT-12:00</option>
                      <option value="<-11>11">GMT-11:00</option>
                      <option value="<-10>10">GMT-10:00</option>
                      <option value="<-09>9">GMT-9:00</option>
                      <option value="<-08>8">GMT-8:00</option>
                      <option value="<-07>7">GMT-7:00</option>
                      <option value="<-06>6">GMT-6:00</option>
                      <option value="<-05>5">GMT-5:00</option>
                      <option value="<-04>4">GMT-4:00</option>
                      <option value="<-03>3">GMT-3:00</option>
                      <option value="<-02>2">GMT-2:00</option>
                      <option value="<-01>1">GMT-1:00</option>
                      <option value="GMT0">GMT+0:00</option>
                      <option value="<+01>-1">GMT+1:00</option>
                      <option value="<+02>-2">GMT+2:00</option>
                      <option value="<+03>-3">GMT+3:00</option>
                      <option value="<+04>-4">GMT+4:00</option>
                      <option value="<+05>-5">GMT+5:00</option>
                      <option value="<+06>-6">GMT+6:00</option>
                      <option value="<+07>-7">GMT+7:00</option>
                      <option value="<+08>-8">GMT+8:00</option>
                      <option value="<+09>-9">GMT+9:00</option>
                      <option value="<+10>-10">GMT+10:00</option>
                      <option value="<+11>-11">GMT+11:00</option>
                      <option value="<+12>-12">GMT+12:00</option>                                  
                    </select>
                  </div>
                  <br>                          
                  <div class="subheader">FTP / HTTPS settings</div>
                  <div class="input-group" id="ftp-group">
                    <label for="fsServer">File Server</label>
                    <input id="fsServer" name="fsServer" length=32 placeholder="File server name">
                  </div>
                  <div class="input-group" id="ftp-group">
                    <label for="fsPort">FS port</label>
                    <input id="fsPort" name="fsPport" length=6 placeholder="File server port">
                  </div>
                  <div class="input-group" id="ftp-group">
                    <label for="ftpUser">FTP user name</label>
                    <input id="ftpUser" name="ftpUser" length=32 placeholder="FTP user name">
                  </div>
                  <div class="input-group" id="ftp-group">
                    <label for="FS_Pass">FS password</label>
                    <input id="FS_Pass" name="FS_Pass" length=32 placeholder="File server password">
                  </div>
                  <div class="input-group" id="ftp-group">
                    <label for="fsWd">FS root dir</label>
                    <input id="fsWd" name="fsWd" length=64 placeholder="Working directory path">
                  </div>
                  <div class="input-group" id="ftp-group">
                    <label for="fsUse">Upload method:</label>FTP&nbsp;
                    <div class="switch">
                      <input id="fsUse" type="checkbox">
                      <label title="Upload file using FTP or HFS" class="slider" for="fsUse"></label>
                    </div>&nbsp;HFS
                  </div>
                  <br>
                  <div class="subheader">SMTP settings</div>
                  <div class="input-group" id="smtp-group">
                    <label for="smtp_server">SMTP server</label>
                    <input id="smtp_server" name="smtp_server" length=32 placeholder="smtp server name">
                  </div>
                  <div class="input-group" id="smtp-group">
                    <label for="smtp_port">SMTP port</label>
                    <input id="smtp_port" name="smtp_port" length=6 placeholder="smtp port">
                  </div>                          
                  <div class="input-group" id="smtp-group">
                    <label for="smtp_login">SMTP login</label>
                    <input id="smtp_login" name="smtp_login" length=32 placeholder="smtp login email">
                  </div>
                  <div class="input-group" id="smtp-group">
                    <label for="SMTP_Pass">SMTP password</label>
                    <input id="SMTP_Pass" name="SMTP_Pass" length=32 placeholder="smtp password">
                  </div>
                  <div class="input-group" id="ftp-group">
                    <label for="smtp_email">SMTP email</label>
                    <input id="smtp_email" name="smtp_email" length=64 placeholder="smtp email to">
                  </div>   
                  <br>
                  <div class="subheader">Authentication settings</div>
                  <div class="input-group" id="auth-group">
                      <label for="Auth_Name">Web login</label>
                      <input id="Auth_Name" name="Auth_Name" length=32 placeholder="Authentication user name">
                  </div>
                  <div class="input-group" id="auth-group">
                      <label for="Auth_Pass">Web password</label>
                      <input id="Auth_Pass" name="Auth_Pass" length=32 placeholder="Authentication password">
                  </div>
                  <div class="input-group" id="auth-group">
                    <label for="useHttps">Use HTTPS</label>
                    <div class="switch">
                      <input id="useHttps" type="checkbox">
                      <label title="Use HTTPS to access app" class="slider" for="useHttps"></label>
                    </div>
                  </div>
                  <div class="input-group" id="auth-group">
                    <label for="useSecure">Check Certs</label>
                    <div class="switch">
                      <input id="useSecure" type="checkbox">
                      <label title="Check remote servers certificate" class="slider" for="useSecure"></label>
                    </div>
                  </div>
                </div>
                <br><br>
              </nav>
              <div id="foot-spacer"></div>
            </div>
          </div>
          <section id="footer"> 
            <div class="info-group center">
                <label for="fw_version">Version</label>
                <div id="fw_version" class="info displayonly">&nbsp;</div>
            </div>
            <div class="info-group center">
                <label for="llevel">Ambient&nbsp;Light</label>
                <div id="llevel" class="info displayonly">&nbsp;</div>
            </div>
            <div class="info-group center">
                <label for="night">Night&nbsp;Time</label>
                <div id="night" class="info displayonly">&nbsp;</div>
            </div>
            <div class="info-group center">
                <label for="atemp">Camera&nbsp;Temp</label>
                <div id="atemp" class="info displayonly">&nbsp;</div>
            </div> 
            <div class="info-group center">
                <label for="battv">Battery&nbsp;Voltage</label>
                <div id="battv" class="info displayonly">&nbsp;</div>
            </div> 
            <div class="info-group center">
                <label for="clock">&nbsp;Camera&nbsp;local&nbsp;time</label>
                <div id="clock" class="info displayonly">&nbsp;</div>
            </div>
            <div class="info-group center">
                <label for="up_time">Up&nbsp;time</label>
                <div id="up_time" class="info displayonly">&nbsp;</div>
            </div>                                                 
            <div class="info-group center">
                <label for="wifi_rssi">Signal&nbsp;Strength</label>
                <div id="wifi_rssi" class="info displayonly">&nbsp;</div>
            </div>
            <div class="info-group center">
                <label for="free_heap">Free&nbsp;heap</label>
                <div id="free_heap" class="info displayonly">&nbsp;</div>
            </div>  
            <div class="info-group center">
                <label for="free_psram">Free&nbsp;PSRAM</label>
                <div id="free_psram" class="info displayonly">&nbsp;</div>
            </div>                 
            <div class="info-group center">
                <label for="total_bytes">Total&nbsp;space</label>
                <div id="total_bytes" class="info displayonly">&nbsp;</div>
            </div>                
            <div class="info-group center">
                <label for="used_bytes">Used&nbsp;space</label>
                <div id="used_bytes" class="info displayonly">&nbsp;</div>
            </div>
            <div class="info-group center">
                <label for="free_bytes">Free&nbsp;space</label>
                <div id="free_bytes" class="info displayonly">&nbsp;</div>
            </div>   
          </section> 
        </div>
        <figure>
          <div id="stream-container" class="image-container hidden">
            <div class="iconSize" id="closeStream" title="Close screen">×</div>
            <div class="iconSize" id="rotate" title="Rotate screen">@</div>
            <div class="iconSize" id="actualSize" title="Actual size">-</div>
            <div class="iconSize" id="maxFit" title="Fit to browser">#</div>
            <div class="iconSize" id="fullAspect" title="Full screen with aspect">&curren;</div>
            <div class="iconSize" id="fullSize" title="Full screen">&rect;</div>
            <div class="iconSize" id="saveImage" title="Download screenshot">v</div>
            <div id="RCenable" class="hidden">
              <div class="RCcontrolFmt RCbuttonFmt" title="Switch RC controls on / off">  
                <svg class="svgIcon">
                  <rect class="RCoff"/>
                  <text class="RCbutton local" id="RCcontrols">🎮</text>
                </svg>
              </div>
            </div>
            <div id="RCdisplay" class="hidden">
              <div class="RCcontrolFmt RClightsFmt" title="Switch RC lights on / off">  
                <svg class="svgIcon">
                  <rect class="RCoff"/>
                  <text class="RCcontrol" id="RClights">💡</text> 
                </svg>
              </div>
              <div class="RCcontrolFmt RCsteerFmt">
                <input title="Control RC steering" type="range" id="steerDirection" class="RCcontrol immed bigThumb ignore" value=0>
                <div name="rangeVal" style="top: 2px" class="rvThumb">0</div> 
              </div>
              <div class="RCcontrolFmt RCmotorFmt">
                <input title="Control RC speed" type="range" id="motorSpeed" class="RCcontrol immed bigThumb ignore" value=0>
                <div name="rangeVal" class="rvThumb">0</div> 
              </div>
            </div>
            <img id="stream" src="" crossorigin>
          </div>
        </figure>
      </div> 
    </div>      
    
    <div id="ShowLog" class="tabcontent">
      <br>
      <div class="grid-cols4">
        <div class="input-group" style="grid-column: span 2; grid-row: span 2">
          <fieldset> 
          <legend id="selectLog">Select Log</legend> 
          <div>
            <label title="Display RTC RAM log" for="rtcram">RTC RAM</label>
            <input class="local" id="rtcram" type="radio" name="logType" value="0" checked>
            <label title="Display Websocket log" for="websock">&nbsp;Websocket</label>
            <input class="local" id="websock" type="radio" name="logType" value="1">
            <label title="Display SD Card log" for="sdcard">&nbsp;SD Card</label>
            <input class="local" id="sdcard" type="radio" name="logType" value="2">
          </div>
          </fieldset>
        </div>
        <div class="input-group" id="dbg-group" style="text-align: left">
          <label for="dbgVerbose" class="header" title="Set verbose logging">Verbose:</label>
        </div>
        <div class="input-group" id="dbg-group" style="text-align: right">
          <div class="switch">
            <input id="dbgVerbose" type="checkbox">
            <label title="Outputs additional information to log" class="slider" for="dbgVerbose"></label>
          </div>
        </div>
        <div class="input-group" id="dbg-group" style="text-align: left">
          <label for="sdLog" class="header" title="Enable logging to sd card">Log to SD:</label>
        </div> 
        <div class="input-group" id="dbg-group" style="text-align: right">
          <div class="switch">
            <input id="sdLog" type="checkbox">
            <label title="Outputs log to SD card" class="slider" for="sdLog"></label>
          </div> 
        </div>
        <div style="grid-column: span 4"><br></div>
        <div>
          <svg>
            <rect/>
            <text class="local" id="refreshLog">Refresh Log</text>
          </svg>
        </div>
        <div>
          <svg>
            <rect/>
            <text class="local" id="clearLog">Clear Log</text>
          </svg>
        </div>
        <div>
          <svg>
            <rect/>
            <text id="save" class="midText">Save</text>
          </svg>
        </div>
        <div>
          <svg>
            <rect/>
            <text id="reset" class="midText">Reboot ESP</text>
          </svg>
        </div>
      </div>
      <br>
      <div id="appLog"></div>
      <br>
    </div>
    
    <div id="EditConfig" class="tabcontent">
      <br>
      <div class="header">Control</div>
      <br>
      <div class="grid-cols4">
        <div>
          <svg>
            <rect/>
            <text id="save" class="midText">Save</text>
          </svg>
        </div>
        <div>
          <svg>
            <rect/>
            <text id="reset" class="midText">Reboot ESP</text>
          </svg>
        </div>
        <div>
          <svg>
            <rect/>
            <text id="deldata" class="midText">Reload /data</text>
          </svg>
        </div>
        <div>
          <svg>
            <rect/>
            <text id="clear" class="midText">Clear NVS</text>
          </svg>
        </div>
        <div class="cfgTitle">
          <br>
          <div class="header">Settings</div>
          <div class="subheader">Press a button to view or modify settings (changed values are not validated)</div>
          <div class="subheader">Press Save button to make changes permanent</div>
          <br>
        </div>
        <div>
          <svg>
            <rect/>
            <text class="local midText" id="wifi">Wifi</text>
          </svg>
        </div>
        <div>
          <svg>
            <rect/>
            <text class="local midText" id="motion">Motion</text>
          </svg>
        </div>
        <div>
          <svg>
            <rect/>
            <text class="local midText" id="peripherals">Peripherals</text>
          </svg>
        </div>
        <div>
          <svg>
            <rect/>
            <text class="local midText" id="other">Other</text>
          </svg>
        </div>
        <div id="RCconfigBtn" class="hidden">
          <svg>
            <rect/>
            <text class="local midText" id="RCconfig">RC Config</text>
          </svg>
        </div>
      </div>
      <div>
        <p class='config-group' id='Cfg'></p>
      </div>
    </div>
    
    <div id="DeviceHub" class="tabcontent">
      <div id="buttonContainer">
        <!-- Input field for users to enter individual IPs -->
        <label for="ipInput">Enter IP address:&nbsp;</label>
        <input type="text" id="ipInput">
        <button id="addIpButton" onclick="addIP()">Add IP</button>
        <button id="clearStorageButton" onclick="clearLocalStorage()">Delete All</button>
        <button id="refreshAllButton" onclick="refreshAllContainers()">Refresh</button>
      </div>
      <!-- Container to hold the image elements -->
      <div id="imageContainer"></div>
    </div>
        
    <div class="alertMsg">
      <span id="alertText"></span>
    </div>
    
    <script>
      'use strict'
      
      let refreshInterval = 5000;
      let imgSize = { width: 0, height: 0 };
      let fitSize = { width: 0, height: 0 };
      let cameraModel = "OV2640";
      let firstCall = true;
      let appClock = Math.ceil(Date.now() / 1000);
      let sustainId = "1";
      const appLogInit = 0;
      const appHub = '/control?still=${Date.now()';

      let view;
      let viewContainer;
      let recordButton;
      let recordingIndicator;
      let streamButton;
      let playbackButton;
      let rotateAngle = 0;
      let RCmode = false;
      let showRC = false; 
      
      // slider orientation is one of:
      //   horizontal : left -> right (default)
      //   vertical   : vertical up for forward
      //   vertInv    : vertical up for reverse / stop
      const steerOrientation = "horizontal";
      const motorOrientation = "vertical"; 
      // following set from app
      let maxSteerAngle = 45;   // max steering angle in degrees from straightahead
      let maxDutyCycle = 100;   // maximum motor duty cycle % (speed)
      let minDutyCycle = 10;    // minimum motor duty cycle % needed to operate motor
      let allowReverse = true;  // set false if reverse motion not required
      let autoControl = true;   // stop motor or center the steering if control not active
      let servoCenter = 90;     // angle in degrees where servo is centered (SG90 style)

      // shorter the waitTime, more responsive RC but more load on ESP32
      let waitTime = 20;        // minimum wait in ms between controller updates


      function appInit() {
        view = $('#stream');
        viewContainer = $('#stream-container');
        recordButton = $('#forceRecord');
        recordingIndicator = $('#recording-indicator');
        streamButton = $('#forceStream');
        playbackButton = $('#forcePlayback');
        $('#steerDirection').min = 0 - maxSteerAngle;
        $('#steerDirection').max = maxSteerAngle;
        $('#steerDirection').classList.add(steerOrientation);
        $('#motorSpeed').max = maxDutyCycle;
        $('#motorSpeed').min = allowReverse ? 0 - maxDutyCycle : 0;
        $('#motorSpeed').classList.add(motorOrientation);
        const RCcfgObserver = new IntersectionObserver((entries) => {
          entries.forEach((entry) => { 
            if (!entry.isIntersecting && isHidden($('#RCdisplay'))) doLoadStatus = true; 
          });
        });
        RCcfgObserver.observe($('#RCconfigBtn'));
      }
      
      async function processStatus(dest, key, value, fromUser = true) {
        // process change to status value
        if (dest == ID) {
          // operation based on ID
          if (RCsliders(key, value)) return;
          if (key == "wifi") getConfig("0");
          else if (key == "hostName") {
            document.title = value;
            if (fromUser) sendControl(key, value);
          } 
          else if (key == "alertMsg") showAlert(value); 
          else if (key == "clearLog") clearLog();
          else if (key == "refreshLog") getLog();
          else if (key == "logType") { logType = value; sendControl(key, value); return; }
          else if (key == "refreshVal") refreshInterval = value * 1000; 
          else if (key == "clockUTC") { appClock = value; checkTime(value); }
          else if (key == "motion") getConfig("01");
          else if (key == "other") getConfig("012");
          else if (key == "peripherals") getConfig("0123");
          else if (key == "sustainId") { if (value >= sustainId) stopAll("sustainId", false); }
          else if (key == "RCconfig") { doLoadStatus = false; getConfig("01234"); }
          else if (key == "camModel") customButtons(key, value);
          else if (key == "delete" || key == "uploadMove" || key == "upload") sdFile(key, value);
          else if (key == 'timezoneSel') fromUser = setTz(value);
          else if (key == "framesize") fromUser = setFrameSize(key, value, fromUser);
          else if (key == "get-still") getStill();
          else if (key == "download" && $("#download").value.includes('.')) doDownload();
          else if (key == "sfile") fromUser = selectFileOrFolder(value, fromUser);
          else if (key == "devHub") {value == "1" ? show($('[name="DeviceHub"]')) : hide($('[name="DeviceHub"]')); }
          else if (key == "aec") value ? hide($('#aec_value-group')) : showAec();
          else if (key == "awb_gain") value ? show($('#wb_mode-group')) : hide($('#wb_mode-group')); 
          else if (key == "agc") setAgc(key, value);
          else if (key == "forceRecord") value = recordAction(key);
          else if (key == "showRecord") setRecIndicator(value);
          else if (key == "forcePlayback") fromUser ? playbackAction(key) : deactivatePlaybackButton();
          else if (key == "forceStream") streamAction(key);
          else if (key == "RCactive") RCstatus(Number(value));
          else if (key == "RClights") value = RCbuttons(key, value);
          else if (key == "RCcontrols") showRCcontrols(RCbuttons(key, value));
          else if (key == "maxSteerAngle") updateMaxSteerAngle(value);
          else if (key == "maxDutyCycle") updateMaxDutyCycle(value);
          else if (key == "minDutyCycle") minDutyCycle = value;
          else if (key == "servoCenter") servoCenter = value;
          else if (key == "allowReverse") { allowReverse = value > 0 ? true : false; updateMaxDutyCycle(maxDutyCycle); }
          else if (key == "autoControl") autoControl = value > 0 ? true : false;
          else if (key == "waitTime") waitTime = value;
          else if (key == 'save') await sleep(600); // allow last input to debounce
          if (fromUser && !$('#'+key).classList.contains("local")) debounceSendControl(key, value); 
        } 
        else if (dest == CLASS) {
          // operations based on class
          if (key.substring(0, 9) == "quick-nav") navigation(value); 
          else if (key == "iconSize") {
            if (value == "saveImage") saveImage();
            else sizeView(value); 
          }
          else if (key == "pin-menu") pinMenu(); 
        }
      }

      /************** menu functions ****************/
         
      function customButtons(key, value) {
        // update camera button text
        if (firstCall) {
          cameraModel = value;
          $('.camTab').innerHTML = cameraModel;
          // customise camera parameters according to camera model
          if (cameraModel == "OV2640") {
            // change button labels
            $('#aec2_lab').innerHTML = "AEC DSP";
            $('#awbg_lab').innerHTML = "AWB Gain";
            $('#dcw_lab').innerHTML = "DCW (Downsize)";
            // change range slider parameters
            changeRange('brightness', "-2", "2", "0");
            changeRange('contrast', "-2", "2", "0");
            changeRange('saturation', "-2", "2", "0");
            changeRange('ae_level', "-2", "2", "0");
            changeRange('gainceiling', "0", "6", "0");
            changeRange('aec_value', "0", "1200", "204");
            changeRange('agc_gain', "0", "30", "5", true);
            $('#gainceiling').parentElement.children.rangeMin.innerHTML = '2x';
            $('#gainceiling').parentElement.children.rangeMax.innerHTML = '128x';
            
          } else if (cameraModel == "OV3660" || cameraModel == "OV5640") {
            // change button labels
            $('#aec2_lab').innerHTML = "Night Mode";
            $('#awbg_lab').innerHTML = "Manual AWB";
            $('#dcw_lab').innerHTML = "Advanced AWB";
            // change range slider parameters
            changeRange('brightness', "-3", "3", "0");
            changeRange('contrast', "-3", "3", "0");
            changeRange('saturation', "-4", "4", "0");
            changeRange('sharpness', "-3", "3", "0");
            changeRange('ae_level', "-5", "5", "0");
            changeRange('gainceiling', "0", "511", "0");
            changeRange('agc_gain', "0", "63", "5", true);
            // show additional options
            $$('div.OV3660, div.OV5640').forEach(el => {show(el)});
            $$('#framesize option').forEach(el => {
              if (el.classList.contains(cameraModel)) show(el);
            });
            let avMax = (cameraModel == "OV3660") ? "1536": "1920"; 
            changeRange('aec_value', "0", avMax, "320");
          } else alert("Unhandled camera model: " + cameraModel);
        }
        firstCall = false;
      }
      
      function changeRange(id, min, max, val, isX = false) {
        // set parameters for range slider
        const range = $('#'+id);
        range.min = min;
        range.max = max;
        range.value = val;
        range.parentElement.children.rangeMin.innerHTML = isX ? (parseInt(min)+1)+'x' : min;
        range.parentElement.children.rangeMax.innerHTML = isX ? (parseInt(max)+1)+'x' : max;
      }
         
      function navigation(key) { 
        const i = $('#'+key); // the clicked icon
        const m = $('#'+i.getAttribute('name')); // the associated menu 

        // toggle selected icon as active, others inactive
        i.classList.toggle('active');
        $$('nav.quick-nav:not(#'+i.id+')').forEach(el => {el.classList.remove('active')});   
        
        // toggle menu attached to icon as active, others inactive
        m.classList.toggle('active');
        $$('nav.panel:not(#'+m.id+')').forEach(el => {el.classList.remove('active')});   
        m.querySelector('.menu-action').checked = isActive(i); // open if icon toggled active, close if not
        
        if (isActive(i)) {
          $('#menu-selector').classList.remove('menu-closed');
          $('#menu-selector').classList.add('menu-open');
        } else {
          $('#menu-selector').classList.add('menu-closed');
          $('#menu-selector').classList.remove('menu-open');
        }
      }

      function pinMenu() {
        // toggle menu pinned status
        if ($('#menu-container').classList.contains('menu-pinned')) {
          $$('.menu-pinned').forEach(el => {
            el.classList.remove('menu-pinned');
            el.classList.add('menu-floating');
          });
        } else {
          $$('.menu-floating').forEach(el => {
            el.classList.remove('menu-floating');
            el.classList.add('menu-pinned');
          });
        }
      }

      /************** stream functions ****************/
      
      async function checkTask(viewURL) {
        // required to get response code as not available from <img> tag
        const response = await fetchRetry(webServer + viewURL, {method: 'HEAD', mode: 'cors'}, 100, 500);
        if (response.ok) {
          sustainId = +appClock + Math.ceil(refreshInterval / 1000);
          return true;
        } else alert(response.status + ": " + response.statusText); 
        return false;
      }
      
      async function doDownload() {
        stopAll("doDownload");
        try {
          if (await checkTask('/sustain?download=0')) window.location.href = webServer + '/sustain?download=0';
        } catch (error) {alert(error);}
      }

      function getStill() {
        hide($('#RCenable'));
        stopAll("getStill"); 
        view.src = webServer + "/control?still="+Date.now(); 
        sustainId = +appClock + Math.ceil(refreshInterval / 1000);
        showView(false);
      }
     
      function recordAction(key) {
        isActive($('#'+key)) ? deactivateRecordButton() : activateRecordButton();
        return isActive($('#'+key)) ? 1 : 0;
      }
              
      function playbackAction(key) {
        isActive($('#'+key)) ? deactivatePlaybackButton() : activatePlaybackButton();
      }      

      function streamAction(key) {
        isActive($('#'+key)) ? deactivateStreamButton() : activateStreamButton();
      } 

      async function checkStream() {
         // check when playback stream ends to close it
        await sleep(2000); // initial delay
        let running = true;
        while (running) {
          try { await view.decode(); }
          catch { running = false; }
        }
        if (!isHidden(viewContainer)) stopAll("checkStream"); 
      }
     
      async function activatePlaybackButton() {
        if (isHidden(recordingIndicator)) {
          if (isActive(streamButton)) alert("Stop streaming first");
          else {
            sendAll("stopPlaying", 1);
            stopAll("activatePlaybackButton");
            playbackButton.classList.add("active");
            playbackButton.innerHTML = "▢&nbsp;Stop Playback";
            playbackButton.classList.add("blinking");
            if (await checkTask('/sustain?playback=0')) {
              view.src = webServer + '/sustain?playback=0'; 
              checkStream();
              hide($('#RCenable'));
              showView(true);
            } else deactivatePlaybackButton();
          } 
        } else alert('Recording in progress');
      }
       
      function deactivatePlaybackButton(sendStop = true) {
        playbackButton.classList.remove("active");
        playbackButton.innerHTML = "➤&nbsp;Start Playback";
        playbackButton.classList.remove("blinking");
        disable(playbackButton);
        stopAll("deactivatePlaybackButton");
        if (sendStop) sendAll("stopPlaying", 1);
      }

      function activateRecordButton() {
        if (!recordButton.classList.contains("active")) {
          recordButton.classList.add("active");
          recordButton.innerHTML = "▢&nbsp;Stop Recording";
        }
        if (isHidden(recordingIndicator)) {
          show(recordingIndicator);
          recordingIndicator.classList.add("blinking");
        }
      }
      
      function deactivateRecordButton() {
        recordButton.classList.remove("active");
        recordButton.innerHTML = "➤&nbsp;Start Recording";
        recordingIndicator.classList.remove("blinking");
        hide(recordingIndicator);
      }

      function setRecIndicator(value) {
        // indicate if motion capture is being recorded
        if (value && isHidden(recordingIndicator)) activateRecordButton();
        // remove recording indicator and deactivate record button if active
        else if (!value && !isHidden(recordingIndicator)) deactivateRecordButton();
      }
      
      async function activateStreamButton() {
        stopAll("activateStreamButton");
        streamButton.classList.add("active");
        streamButton.innerHTML = "▢&nbsp;Stop Stream";
        streamButton.classList.add('blinking');
        showRC ? show($('#RCenable')) : hide($('#RCenable')); 
        if (await checkTask('/sustain?stream=0')) {
          view.src = webServer + '/sustain?stream=0'; 
          showView(false);
        } else deactivateStreamButton();
      }
      
      function deactivateStreamButton(sendStop = true) {
        streamButton.classList.remove("active");
        streamButton.innerHTML ="➤&nbsp;Start Stream";
        streamButton.classList.remove('blinking');
        stopAll("deactivateStreamButton");
        if (sendStop) sendAll("stopStream", "0");
      }

      async function stopAll(caller, sendStop = true) {
        if (!isHidden(viewContainer)) {
          stopRC();
          if (isSecure) await sleep(1000); // otherwise TLS crash
          window.stop();
        }
        if (isActive(playbackButton)) deactivatePlaybackButton(sendStop);
        if (isActive(streamButton)) deactivateStreamButton(sendStop);
        hide(viewContainer);
      }

      function showView(isFile) {
        // set size values for given framesize
        let sizeStr = '800x600';
        if (isFile) {
          // get framesize from filename  
          sizeStr = $('#sfile').value.split('_')[2]; // the frame type eg SVGA
          $$('#framesize option').forEach(el => { 
            if (el.innerHTML.substr(0, sizeStr.length) == sizeStr) sizeStr = el.innerHTML; // now contains eg SVGA(800x600)
          });
        } else sizeStr = $('#framesize option:checked').innerHTML; // get framesize from selected framesize
           
        // determine actual image size using framesize pixels
        const fwidth =  parseInt(sizeStr.split('x')[0].match(/\d+/));
        const fheight = parseInt(sizeStr.split('x')[1].match(/\d+/));
        imgSize = {width: fwidth, height: fheight};
        sizeView('maxFit'); // initial view size
      }
        
      function calculateAspectRatioFit(srcWidth, srcHeight, maxWidth, maxHeight) {
        const ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
        return { width: Math.round(srcWidth*ratio), height: Math.round(srcHeight*ratio) };
      }
      
      function sizeView(selectedSize) {  
        // stream icons to set size of screen
        // - max size to fit browser window for aspect ratio
        // # actual frame size
        // ¤ fullscreen with correct aspect ratio (lose some)
        // ▭ fullscreen (aspect ratio changed)
        // x close screen
        // v download still image 
        
        if (selectedSize == 'closeStream') {
          stopAll("closeStream");
          sendAll("stopStream", "0");
        }
        else {
          if (selectedSize == 'maxFit') {
            fitSize = calculateAspectRatioFit(imgSize.width, imgSize.height, 
            window.screen.availWidth - $('#menu-container').offsetWidth - 25, 
            window.innerHeight - $('.tab').offsetHeight - $('#footer').offsetHeight - 25);
            viewContainer.style.cssText = 'width:'+fitSize.width+'px; height:'+fitSize.height+'px';
          }
          else if (selectedSize == 'actualSize') {
            viewContainer.style.cssText = 'width:'+imgSize.width+'px; height:'+imgSize.height+'px';
          }
          else if (selectedSize == 'fullAspect') {
            if (viewContainer.requestFullscreen) viewContainer.requestFullscreen();
            else viewContainer.webkitRequestFullscreen();
            const r = calculateAspectRatioFit(imgSize.width, imgSize.height, window.screen.availWidth, window.screen.availHeight)
            viewContainer.style.cssText = 'width:'+r.width+'px; height:'+r.height+'px';
            view.style.cssText = "width: 100%; height:auto";
          }
          else if (selectedSize == 'fullSize') {
            if (viewContainer.requestFullscreen) viewContainer.requestFullscreen();
            else viewContainer.webkitRequestFullscreen();
            viewContainer.style.cssText = 'width:'+window.screen.availWidth+'px; height:'+window.screen.availHeight+'px';
            view.style.cssText = "width: 100%; height: 100%";
          }
          else if (selectedSize == 'rotate') {
            rotateAngle = rotateAngle == 270 ? 0 : rotateAngle + 90;
            const scale = (rotateAngle == 90 || rotateAngle == 270) ? imgSize.height / imgSize.width : 1.0;
            view.style.transform = 'rotate('+rotateAngle+'deg) scale(' + scale + ')'; 
          }
          if ((selectedSize == 'actualSize' || selectedSize == 'maxFit') && document.fullscreenElement !== null) {
            if (document.exitFullscreen) document.exitFullscreen();
            else document.webkitExitFullscreen(); /* Safari */
          }
          
          // locate size buttons on screen
          let buttonPos = baseFontSize / 4;
          $$('.iconSize').forEach(el => {
            if (el.id != selectedSize || el.id == "rotate") {
              el.style.right = buttonPos + 'px'; 
              buttonPos += baseFontSize * 1.25;
              show(el);
            } else hide(el);
          });
          show(viewContainer);
        }
      }
      
      function saveImage() {
        const canvas = document.createElement("canvas");
        canvas.width = imgSize.width;
        canvas.height = imgSize.height;
        document.body.appendChild(canvas);
        canvas.getContext('2d').drawImage(view,0,0);
        let anchor = document.createElement("a");
        anchor.download = $('#clock').innerHTML+".jpg";
        anchor.href = canvas.toDataURL("image/jpeg");
        anchor.click();
        anchor.remove();
        canvas.parentNode.removeChild(canvas);
      }
      

      /************* user selection functions ************/

      function selectFileOrFolder(filePath, fromUser) {
        // build folder / file option list from json
        let pathDir = "";
        if (filePath != '/~reset') {
          $('#download').value = filePath; //Store file path for download
          $('#delete').value = filePath; //Store file path for delete
          $('#upload').value = filePath; //Store file path for file server upload
          pathDir = filePath.substring(0, filePath.lastIndexOf("/"));
        }
        if (pathDir == "") $$('#sfile option:not(:first-child)').forEach(el => { el.remove()}); 
        if (filePath.substr(-4) === '.avi' && !isActive(playbackButton)) enable(playbackButton);
        if (fromUser) getFiles(filePath);
        return false;
      }
      
      async function getFiles(filePath) {
        // get list of files / folders
        $$('*').forEach(el => { el.style.cursor = ("wait")});
        const response = await fetch(encodeURI(webServer + '/control?sfile='+filePath));
        if (response.ok) {
          const fileData = await response.json();
          Object.entries(fileData).forEach(([key, value]) => {
            if (!value.includes("System") && !value.includes("FOUND")) {
              let option = document.createElement("option");
              option.text = value;
              option.value = key;
              $('#sfile').add(option);
            }
          });
        } else alert(response.status + ": " + response.statusText);
        await sleep(500); 
        $$('*').forEach(el => { el.style.cursor = ("default")});
      }
      
      function setFrameSize(key, value, fromUser) {
        if (fromUser) {
          sendControl(key, value);
          sendControlResp("updateFPS", value); // needs to be after above
        }
        return false;
      }
      
      function setAgc(key, value) {
        if (value) {
          show($('#gainceiling-group'));
          hide($('#agc_gain-group'));
          rangeSlider($('#gainceiling'));
        } else {
          hide($('#gainceiling-group'));
          show($('#agc_gain-group'));
          rangeSlider($('#agc_gain'));
        }
      }
      
      function showAec() {
        show($('#aec_value-group'));
        rangeSlider($('#aec_value'));
      }
 
      function sdFile(key, value) {
        if (key == "delete") {
          if (!confirm("Are you sure you want to delete " + value + " from the SD card?")) return false;
        }
        sendControl(key, value);
        if (key == "delete" || key == "uploadMove") selectFileOrFolder("/~reset", true);
      }
      
      function sendAll(key, val) {
        sendControl(key, val);
        if (ws != null) ws.send('C' + key + '=' + val);
      }
      
      function configStatus(refresh) {} 
      
      function closedTab(isClosed) {
        if (isClosed) stopAll("closedTab");
      }
      
      /************** RC ****************/
      
      function RCstatus(doShow) {
        if (doShow) {
          showRC = true;
          show($('#RCconfigBtn'));
        } else { 
          showRC = false;
          hide($('#RCconfigBtn'));
        }
      }
      
      async function showRCcontrols(showControls) {
        if (showControls) {
          // start rc control
          show($('#RCdisplay')) 
          RCmode = true;
          loggingOn = false;
          while (ws.readyState != WebSocket.OPEN) await sleep(50); // allow websocket to start
          doLoadStatus = false;
        } else stopRC();
      }

      function stopRC() {
        resetButton('RClights');
        resetButton('RCcontrols');

        // stop rc control
        hide($('#RCdisplay'));
        doLoadStatus = true;
        // close off RC activity
        if (RCmode) {
          ws.send('D' + servoCenter);
          ws.send("M0");
          debounceSendControl('RClights', '0'); 
        }
        RCmode = false;
        if (logType == 1) loggingOn = true; 
      }
      
      function resetButton(buttonId) {
        const rect = $('#'+buttonId).previousElementSibling;
        if (rect.classList.contains('RCon')) { 
          rect.classList.remove('RCon');
          rect.classList.add('RCoff');
        }
      }
      
      // minimum period of RC changes sent to esp32
      let lastSend = 0;
      function readyToSend() {
        // prevent input overload on esp32
        const now = (new Date).getTime();
        if (now - lastSend > waitTime) {
          lastSend = now;
          return true;
        } else return false;
      }
      
      function RCsliders(key, value) {
        // RC control
        let checkedRC = false;
        if (ws != null) {
          let doSend = false;
          let val = parseInt(value);
          if (key == "steerDirection" || key == "motorSpeed") {
            if (readyToSend()) doSend = true;
            checkedRC = true;
            if (autoControl && !isImmed) {
              // auto centre steering or stop motor if focus lost
              val = 0; 
              const e = $('#'+key);
              e.parentElement.children.rangeVal.innerHTML = '0';
              rangeSlider(e, false);
              doSend = true;
            }  
          }
          if (doSend) {
            if (key == "steerDirection") {
              val += 1 * servoCenter;
              ws.send("D" + val);
            }
            else if (key == "motorSpeed") {
              if (Math.abs(val) < minDutyCycle) val = 0;
              ws.send("M" + val);
            }
          }
        }
        isImmed = false;
        return checkedRC;
      }
      
      function RCbuttons(key, value) {
        // RC buttons processing
        const rect = $('#'+key).previousElementSibling;
        if (rect.classList.contains('RCon')) {
          rect.classList.remove('RCon');
          rect.classList.add('RCoff');
        } else {
          rect.classList.remove('RCoff');
          rect.classList.add('RCon');
        }
        const val = rect.classList.contains('RCon') ? 1 : 0;
        return val;
      }
      
      function updateMaxSteerAngle(val) {
        maxSteerAngle = val;
        $('#steerDirection').min = 0 - maxSteerAngle;
        $('#steerDirection').max = maxSteerAngle;
      }
      
      function updateMaxDutyCycle(val) {
        maxDutyCycle = val;
        $('#motorSpeed').max = maxDutyCycle;
        $('#motorSpeed').min = allowReverse ? 0 - maxDutyCycle : 0;
      }

      /***************************************************************************/
      
      // config for common script - leave as is
      const doCustomInit = false;
      const doInitWebSocket = true;
      let doLoadStatus = true;
      const doRefreshTimer = true;
      const doCustomSync = false;
      const doHeartbeat = false;

      const scriptFiles = ['/web?common.js', 'C:/MyStuff/Arduino/CommonMaster/Latest/common.js']; 
      for (let i = 0; i < scriptFiles.length; i++) {
        const scriptElement = document.createElement('script');
        scriptElement.src = scriptFiles[i];
        document.body.appendChild(scriptElement);
      }
      
      window.addEventListener('load', function() {
        initialise();
        appInit();
      });
      
    </script>
  </body>
</html>
